> Android在线手册 > Android学习系列(28)--App集成支付宝[已过期]

手机的在线支付,被认为是2012年最看好的功能,我个人认为这也是移动互联网较传统互联网将会大放光彩的一个功能。
人人有手机,人人携带手机,花钱买东西,不再需要取钱付现,不再需要回家上网银,想买什么,扫描一下,或者搜索一下,然后下单,不找零,直接送到你家,这将是手机支付给我们带来的全新交易体验。
谷歌刚推出了谷歌钱包,这必是我们后面要使用的主要手段,但是鉴于当前国情,我觉得有必要介绍一下android手机集成支付宝功能。 

1.下载官方架包和说明文档
其实官方已经提供了安装指南,下载地址:
Https://mobiless.alipay.com/product/product_down_load.htm?code=SECURITY_PAY
里面有有个pdf,详细说明了说用指南,写的比较详细,可以重点参考。

Android学习系列(28)--App集成支付宝[已过期]


下载下来,我们主要是用到Android(20120104)目录下的alipay_plugin.jar和AppDemo/assets下的alipay_plugin223_0309.apk,这两个文件是我们不能修改的支付宝api和安装包。

2. 商户签约
现在的安全机制,都是这样,客户端需要先和服务端请求验证后才能进行进一步操作,oauth也是如此。
打开https://ms.alipay.com/,登陆支付宝,点击签约入口,选择"应用类产品",填写并等待审核,获取商户ID和账户ID。
签约的时候还要向需要提供实名认证和上传应用,所以我建议先把应用做好了,最后再集成支付宝。

Android学习系列(28)--App集成支付宝[已过期]
我大概等了1-2天审核,审核是失败的,回复是应用类型啥的应该是"虚拟货币",我改成那个马上自动就审核通过了。

3.密钥配置
解压openssl-0.9.8k_WIN32(RSA密钥生成工具).zip,打开cmd,命令行进入openssl-0.9.8k_WIN32(RSA密钥生成工具)\bin目录下,
(1).执行

openssl genrsa  -out rsa_private_key.pem 1024

生成rsa_private_key.pem文件。
(2).再执行

openssl rsa  -in rsa_private_key.pem  -pubout -out rsa_public_key.pem

生成rsa_public_key.pem 文件。
(3).在执行

openssl pkcs8  -topk8  -inform PEM  -in rsa_private_key.pem  -outform PEM  -nocrypt

将RSA私钥转换成 PKCS8 格式,去掉begin和end那两行,把里面的内容拷贝出来,保存到某个txt中,如rsa_private_pkcs8_key.txt中(我好像没用到这个)。
打开rsa_public_key.pem,即商户的公钥,复制到一个新的TXT中,删除文件头”-----BEGIN PUBLIC KEY-----“与文件尾”-----END PUBLIC KEY-----“还有空格、换行,变成一行字符串并保存该 TXT 文件,然后在网站的“我的商家服务”切换卡下的右边点击“密钥管理”,然后有个"上传商户公钥(RSA)"项,选择上传刚才的TXT文件.
好了,服务器配置OK,因为这一段之前没有截图,现在弄好了又不好截图,如果有不明白的地方请大家参考官方文档。 

4.引用jar和包含安装包
    (1).新建android工程;
    (2).copy上面说的alipay_plugin.jar到工程的libs目录下,并在java build path中通过Add External JARs找到并引用该jar;
    (3).copy上面说的alipay_plugin223_0309.apk安装包到assets目录下,后面配置路径用到。

Android学习系列(28)--App集成支付宝[已过期]

如果libs和assets目录没有,手动建立者两个目录。

5.调用代码整理
这里我们要严重的参考文档中AppDemo,我们建一个包com.tianxia.lib.baseworld.alipay,把AppDemo的com.alipay.android.appDemo4包下的源码全部copy到刚才我们自己的包下,还有res目录下的资源文件也合并到我们工程res下。
其中AlixDemo.java,ProductListAdapter.java,Products.java是示例类,我们借鉴完后可以删除。
PartnerConfig.java是配置类,配置商户的一些配置参数。
其他的类是严重参考类,直接留下使用。
PartnerConfig.java代码如下:

public class PartnerConfig {
	//合作商户ID。用签约支付宝账号登录ms.alipay.com后,在账户信息页面获取。
	public static final String PARTNER = "xxx";
	//账户ID。用签约支付宝账号登录ms.alipay.com后,在账户信息页面获取。
	public static final String SELLER = "xxx";
	//商户(RSA)私钥 ,即rsa_private_key.pem中去掉首行,最后一行,空格和换行最后拼成一行的字符串
	public static final String RSA_PRIVATE = "xxx";
	//支付宝(RSA)公钥  用签约支付宝账号登录ms.alipay.com后,在密钥管理页面获取。
	public static final String RSA_ALIPAY_PUBLIC = "xxx";
	//下面的配置告诉应用去assets目录下找安装包
	public static final String ALIPAY_PLUGIN_NAME ="alipay_plugin223_0309.apk";
}

AlixDemo中代码是最终的调用代码在onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {}中,下面我们提取其中的核心代码。

6.提取核心调用代码
在AlixDemo.java同目录下新建AlixPay.java,来提取AlixDemo.java的核心代码:

package com.tianxia.lib.baseworld.alipay;

import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.tianxia.lib.baseworld.R;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.widget.Toast;

public class AlixPay {

    static String TAG = "AlixPay";

    private Activity mActivity;
    public AlixPay(Activity activity) {
        mActivity = activity;
    }

    private ProgressDialog mProgress = null;

    // the handler use to receive the pay result.
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            try {
                String strRet = (String) msg.obj;

                switch (msg.what) {
                case AlixId.RQF_PAY: {

                    closeProgress();

                    BaseHelper.log(TAG, strRet);

                    try {
                        String memo = "memo=";
                        int imemoStart = strRet.indexOf("memo=");
                        imemoStart += memo.length();
                        int imemoEnd = strRet.indexOf(";result=");
                        memo = strRet.substring(imemoStart, imemoEnd);

                        ResultChecker resultChecker = new ResultChecker(strRet);

                        int retVal = resultChecker.checkSign();
                        if (retVal == ResultChecker.RESULT_CHECK_SIGN_FAILED) {
                            BaseHelper.showDialog(
                                    mActivity,
                                    "提示",
                                    mActivity.getResources().getString(
                                            R.string.check_sign_failed),
                                    android.R.drawable.ic_dialog_alert);
                        } else {
                            BaseHelper.showDialog(mActivity, "提示", memo,
                                    R.drawable.infoicon);
                        }
                        
                    } catch (Exception e) {
                        e.printStackTrace();

                        BaseHelper.showDialog(mActivity, "提示", strRet,
                                R.drawable.infoicon);
                    }
                }
                    break;
                }

                super.handleMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    // close the progress bar
    void closeProgress() {
        try {
            if (mProgress != null) {
                mProgress.dismiss();
                mProgress = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void pay() {
        MobileSecurePayHelper mspHelper = new MobileSecurePayHelper(mActivity);
        boolean isMobile_spExist = mspHelper.detectMobile_sp();
        if (!isMobile_spExist)
            return;

        if (!checkInfo()) {
            BaseHelper.showDialog(mActivity, "提示",
                    "缺少partner或者seller,", R.drawable.infoicon);
            return;
        }

        try {
            // prepare the order info.
            String orderInfo = getOrderInfo();
            String signType = getSignType();
            String strsign = sign(signType, orderInfo);
            strsign = URLEncoder.encode(strsign);
            String info = orderInfo + "&sign=" + "\"" + strsign + "\"" + "&"
                    + getSignType();
            
            // start the pay.
            MobileSecurePayer msp = new MobileSecurePayer();
            boolean bRet = msp.pay(info, mHandler, AlixId.RQF_PAY, mActivity);
            
            if (bRet) {
                // show the progress bar to indicate that we have started paying.
                closeProgress();
                mProgress = BaseHelper.showProgress(mActivity, null, "正在支付", false,
                        true);
            } else
                ;
        } catch (Exception ex) {
            Toast.makeText(mActivity, R.string.remote_call_failed,
                    Toast.LENGTH_SHORT).show();
        }
        
    }

    private boolean checkInfo() {
        String partner = PartnerConfig.PARTNER;
        String seller = PartnerConfig.SELLER;
        if (partner == null || partner.length() <= 0 || seller == null
                || seller.length() <= 0)
            return false;

        return true;
    }


    // get the selected order info for pay.
    String getOrderInfo() {
        String strOrderInfo = "partner=" + "\"" + PartnerConfig.PARTNER + "\"";
        strOrderInfo += "&";
        strOrderInfo += "seller=" + "\"" + PartnerConfig.SELLER + "\"";
        strOrderInfo += "&";
        strOrderInfo += "out_trade_no=" + "\"" + getOutTradeNo() + "\"";
        strOrderInfo += "&";
        //这笔交易价钱
        strOrderInfo += "subject=" + "\"" + mActivity.getString(R.string.donate_subject) + "\"";
        strOrderInfo += "&";
        //这笔交易内容
        strOrderInfo += "body=" + "\"" + mActivity.getString(R.string.donate_body) + "\"";
        strOrderInfo += "&";
        //这笔交易价钱
        strOrderInfo += "total_fee=" + "\"" + "10.00" + "\"";
        strOrderInfo += "&";
        strOrderInfo += "notify_url=" + "\""
                + "http://notify.java.jpxx.org/index.jsp" + "\"";

        return strOrderInfo;
    }

    // get the out_trade_no for an order.
    String getOutTradeNo() {
        SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss");
        Date date = new Date();
        String strKey = format.format(date);

        java.util.RanDOM r = new java.util.Random();
        strKey = strKey + r.nextInt();
        strKey = strKey.substring(0, 15);
        return strKey;
    }

    // get the sign type we use.
    String getSignType() {
        String getSignType = "sign_type=" + "\"" + "RSA" + "\"";
        return getSignType;
    }

    // sign the order info.
    String sign(String signType, String content) {
        return Rsa.sign(content, PartnerConfig.RSA_PRIVATE);
    }

    // the OnCancelListener for lephone platform.
    static class AlixOnCancelListener implements
            DialogInterface.OnCancelListener {
        Activity mcontext;

        AlixOnCancelListener(Activity context) {
            mcontext = context;
        }

        public void onCancel(DialogInterface dialog) {
            mcontext.onKeyDown(KeyEvent.KEYCODE_BACK, null);
        }
    }
}

这个类的pay方法就是支付的方法,最简单的不设置的话,调用方法如下:

AlixPay alixPay = new AlixPay(SettingTabActivity.this);
alixPay.pay();

如果没有安装支付宝,它会提示你安装,如果已经安装,它直接让你选择付款:

Android学习系列(28)--App集成支付宝[已过期]

这说明已经配置成功了。
然后可以删掉那些示例java文件了: AlixDemo.java,ProductListAdapter.java,Products.java。 
你也可以通过调整参数来修改订单信息,如主题,价格等。
另外在BaseHelper的94行:

dialog.setOnCancelListener( new AlixDemo.AlixOnCancelListener( (Activity)context ) );

需要修改为:

dialog.setOnCancelListener( new AlixPay.AlixOnCancelListener( (Activity)context ) );

7.注意
我在测试的时候,调用的activity是框在一个ActivityGroup里的(与tabhost类似,据说tabhost也有这个问题),导致MobileSecurePayer.java的pay方法中调用服务的两行代码:

mActivity.bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE);
mActivity.unbindService(mAlixPayConnection);

需要修改为:

mActivity.getApplicationContext().bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE);
mActivity.getApplicationContext().unbindService(mAlixPayConnection);

不然会报错java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.android.server.am.ActivityRecord$Token...

8.小结
支付宝的集成比我想象的要复杂一些,比较麻烦,首先需要审核,然后代码需要提取,所以写出来与大家分享。 
在做集成配置的时候,一定要仔细认真,一个地方出错,可能要导致后面查错查很长时间。
因为本人是先集成成功后才写的这篇文章,难免会漏掉一些重要的细节或者步骤,如有不对,请留言指正。